﻿using System;
using System.Collections.Generic;
using System.IO;
using System.IO.Compression;
using System.Net;
using System.Net.Security;
using System.Runtime.Serialization;
using System.Security.Cryptography.X509Certificates;
using System.Security.Permissions;
using System.Text;
using System.Text.RegularExpressions;
using System.Web;
using System.Web.Caching;
using DeGiglio.biz.Twitter.Properties;
using DeGiglio.biz.Twitter.Resources;

namespace DeGiglio.biz.Twitter.Classes
{
    internal class TwitterHelper
    {
        #region " Cache Items and Methods "
        private string CACHE_NAME;
        private string CACHE_NAME_ACCESS_TOKEN;

        /// <summary>
        /// The SetCacheName method creates a name for cache objects based on the retrieved screen name and list name from Twitter.
        /// We create a dynamic cache name so that the component can be used multiple times on a server and the cache requests won't 
        /// step on each other.
        /// </summary>
        private void SetCacheName()
        {
            CACHE_NAME = "TweetList" + this.ScreenName + this.ListName;
        }

        private void SetAccessTokenCacheName()
        {
            CACHE_NAME_ACCESS_TOKEN = "TwitterAccessToken_" + this.Twitter_ConsumerKey;
        }

        /// <summary>
        /// Add a tweet collection to the web server's cache
        /// </summary>
        /// <param name="tweetCollection"></param>
        //private void SetCache(List<Tweet> tweetCollection)
        private void SetCache(string cacheKeyName, object objectToCache)
        {
            HttpRuntime.Cache.Add(cacheKeyName, // Key
                                  objectToCache, // Object
                                  null, // Dependency
                                  DateTime.Now.AddMinutes(this.CacheTimespan), // Expiration
                                  Cache.NoSlidingExpiration, // Sliding Expiration Type
                                  CacheItemPriority.Normal, // Cache priority
                                  null); // Callback
        }

        /// <summary>
        /// Retrieve a tweet collection from the web server's cache
        /// </summary>
        /// <returns></returns>
        //private List<Tweet> GetCache()
        private object GetCache(string cacheKeyName)
        {
            return HttpRuntime.Cache.Get(cacheKeyName);
        }

        /// <summary>
        /// Clear a specific item from cache
        /// </summary>
        private void ClearCache(string cacheKey)
        {
            HttpRuntime.Cache.Remove(cacheKey);
        }
        #endregion

        #region " Constructors "
        /// <summary>
        /// This constructor is used when you want to explicitly set the request type and/or
        /// will not be requesting a tweet collection (e.g. getting the rate limit)
        /// </summary>
        /// <param name="type"></param>
        internal TwitterHelper(RequestType type)
        {
            this.ApiRequest = type;
        }

        /// <summary>
        /// Constructor for automatically setting the GetUserTweets request type
        /// </summary>
        /// <param name="screenName">The Twitter username</param>
        /// <param name="requestedItems">The number of tweets to retrieve</param>
        /// <param name="includeRetweets">Include retweets in the retrieved items</param>
        internal TwitterHelper(string screenName, int requestedItems, bool includeRetweets)
        {
            this.ScreenName = screenName;
            this.RequestedItems = requestedItems;
            this.IncludeRetweets = includeRetweets;
            this.ApiRequest = RequestType.GetUserTweets;
            SetCacheName();
        }

        /// <summary>
        /// Constructor for automatically setting the GetListTweets request type
        /// </summary>
        /// <param name="listName">The name of the curated list to retrieve</param>
        /// <param name="screenName">The Twitter username</param>
        /// <param name="requestedItems">The number of tweets to retrieve</param>
        /// <param name="includeRetweets">Include retweets in the retrieved items</param>
        internal TwitterHelper(string listName, string screenName, int requestedItems, bool includeRetweets)
        {
            this.ListName = listName;
            this.ScreenName = screenName;
            this.RequestedItems = requestedItems;
            this.IncludeRetweets = includeRetweets;
            this.ApiRequest = RequestType.GetListTweets;
            SetCacheName();
        }
        #endregion

        #region " Class Properties "
        internal RequestType ApiRequest { get; set; }
        internal string ScreenName { get; set; }
        internal string ListName { get; set; }
        internal int RequestedItems { get; set; }
        internal bool IncludeRetweets { get; set; }
        internal int CacheTimespan
        {
            get { return this.cacheTimespan; }
            set { this.cacheTimespan = value; }
        }

        internal AuthenticationType AuthenticateAs
        {
            get { return this.authAs; }
            set { this.authAs = value; }
        }
        internal string Twitter_ConsumerKey
        {
            get { return this.consumerKey; }
            set
            {
                this.consumerKey = value;
                SetAccessTokenCacheName();
            }
        }

        internal string Twitter_ConsumerSecret { get; set; }
        internal int UICultureLocaleId
        {
            get { return this.displayLocaleId; }
            set { this.displayLocaleId = value; }
        }
        #endregion

        #region " Enumerations "
        internal enum RequestType
        {
            GetAccessToken,
            GetUserTweets,
            GetListTweets,
            GetRateLimit,
            UserLookup,
            ClearCache
        }

        internal enum AuthenticationType
        {
            NotSet,
            Authenticated
        }
        #endregion

        #region " Private Class Variables "
        private AuthenticationType authAs = AuthenticationType.NotSet;
        private int displayLocaleId = System.Globalization.CultureInfo.CurrentUICulture.LCID;
        private int cacheTimespan = ProjectSettings.Default.CacheTimespan_DefaultValue;
        private string consumerKey;
        private bool certValidationComplete = false;
        #endregion

        #region " Externally Accessible Methods "
        internal List<Tweet> GetTweets(bool loadAvatarUsingSsl)
        {
            if (this.AuthenticateAs == AuthenticationType.NotSet)
            {
                throw new ArgumentException(Common.GetResourceValue("ExceptionMessage_AuthNotSet", this.displayLocaleId));
            }

            List<Tweet> tweetCollection = new List<Tweet>();
            tweetCollection = GetCache(this.CACHE_NAME) as List<Tweet>;

            if (tweetCollection == null)
            {
                string response = GetTwitterResponse(this.ApiRequest, "GET", true, string.Empty);
                List<TwitterResponse_Tweet> tweets = Common.DeserializeJson<List<TwitterResponse_Tweet>>(response);

                tweetCollection = new List<Tweet>();

                foreach (TwitterResponse_Tweet status in tweets)
                {
                    Tweet tweet = new Tweet();
                    tweet.Id = status.Id;
                    tweet.CreatedAt = ParseTwitterDate(status.CreatedAt);
                    tweet.DisplayTime = DetermineDisplayDiff(tweet.CreatedAt);
                    tweet.Status = ParseStatusMessage(status.Text);
                    tweet.UserName = status.User.ScreenName;

                    if (loadAvatarUsingSsl)
                    {
                        tweet.AvatarUrl = status.User.ProfileImageUrlHttps;
                    }
                    else
                    {
                        tweet.AvatarUrl = status.User.ProfileImageUrl;
                    }

                    tweet.StatusUri = string.Format(ProjectSettings.Default.TwitterUrlTemplate_StatusLink, tweet.UserName, tweet.Id);
                    tweet.ReplyUri = string.Format(ProjectSettings.Default.TwitterUrlTemplate_Reply, tweet.Id);
                    tweet.RetweetUri = string.Format(ProjectSettings.Default.TwitterUrlTemplate_Retweet, tweet.Id);
                    tweet.FavoriteUri = string.Format(ProjectSettings.Default.TwitterUrlTemplate_Favorite, tweet.Id);
                    tweet.UserUri = string.Format(ProjectSettings.Default.TwitterUrlTemplate_UserLink, tweet.UserName);

                    tweetCollection.Add(tweet);
                }

                SetCache(this.CACHE_NAME, tweetCollection);
            }
            return tweetCollection;
        }

        internal RateLimit GetRateLimit()
        {
            if (this.AuthenticateAs == AuthenticationType.NotSet)
            {
                throw new ArgumentException(DeGiglio_biz_Twitter_LocalizedStrings.ExceptionMessage_AuthNotSet);
            }

            string response = this.GetTwitterResponse(RequestType.GetRateLimit, "GET", true, string.Empty);
            TwitterResponse_RateLimit results = Common.DeserializeJson<TwitterResponse_RateLimit>(response);

            RateLimit rateLimit = new RateLimit();
            rateLimit.Limit = results.Resources.Statuses.UserTimeLine.Limit;
            rateLimit.RemainingLimit = results.Resources.Statuses.UserTimeLine.Remaining;
            rateLimit.ResetTime = Common.FromUnixTime(results.Resources.Statuses.UserTimeLine.Reset).ToLocalTime();
            return rateLimit;
        }

        internal void ClearTweetCache()
        {
            SetCacheName();
            this.ClearCache(this.CACHE_NAME);
        }
        #endregion

        #region " Twitter API Call Construction Methods "
        /// <summary>
        /// The GetRequestUri method is used to abstract the calls to specific API builder methods
        /// </summary>
        /// <param name="type">The type of Twitter call to make</param>
        /// <returns>The full URI (with params) to be used to call Twitter</returns>
        private string GetRequestUri(RequestType type)
        {
            string uri = string.Empty;
            switch (type)
            {
                case RequestType.GetAccessToken:
                    uri = ProjectSettings.Default.TwitterUrl_AuthToken;
                    break;
                case RequestType.GetListTweets:
                    uri = BuildListRequestUri();
                    break;
                case RequestType.GetUserTweets:
                    uri = BuildStatusRequestUri();
                    break;
                case RequestType.GetRateLimit:
                    uri = BuildRateLimitRequestUri();
                    break;
                case RequestType.UserLookup:
                    uri = BuildUserLookupRequestUri();
                    break;
            }

            return uri;
        }

        #region BuildListRequestUri
        /* Fields as listed in the API documentation (https://dev.twitter.com/docs/api/1/get/lists/statuses):
         * list_id // not in use for this component
         * slug
         * owner_screen_name
         * owner_id // not in use for this component
         * since_id // not in use for this component
         * max_id // not in use for this component
         * per_page
         * page // will be hard-coded to 1
         * include_entities // will be hard-coded to false
         * include_rts
        */

        /// <summary>
        /// The BuildListRequestUri method is used to build the call uri for retrieving a curated list. 
        /// This overload assumes that the list name, screen name, requested items and retweet properties have been assigned.
        /// </summary>
        /// <returns>The fully constructed uri (with parameters)</returns>
        private string BuildListRequestUri()
        {
            return BuildListRequestUri(this.ListName, this.ScreenName, this.RequestedItems, this.IncludeRetweets);
        }

        /// <summary>
        /// The BuildListRequestUri method is used to build the call uri for retrieving a curated list.
        /// </summary>
        /// <returns>The fully constructed uri (with parameters)</returns>
        private static string BuildListRequestUri(string listName, string screenName, int requestedItems, bool includeRetweets)
        {
            return string.Format(ProjectSettings.Default.TwitterUrlTemplate_GetList, listName, screenName, requestedItems.ToString(), includeRetweets.ToString());
        }
        #endregion

        #region BuildStatusRequestUri
        /* Fields as listed in the API documentation (https://dev.twitter.com/docs/api/1/get/statuses/user_timeline):
         * user_id // not in use for this component
         * screen_name
         * since_id // not in use for this component
         * count
         * max_id // not in use for this component
         * page // will be hard-coded to 1
         * trim_user // not in use for this component (defaulting to false)
         * include_rts
         * include_entities // will be hard-coded to false
         * exclude_replies // not in use for this component
         * contributor_details // will be hard-coded to false
        */
        /// <summary>
        /// The BuildStatusRequestUri method is used to build the call uri for retrieving a specific user's tweets 
        /// This overload assumes that the screen name, requested items and retweet properties have been assigned.
        /// </summary>
        /// <returns>The fully constructed uri (with parameters)</returns>
        private string BuildStatusRequestUri()
        {
            return BuildStatusRequestUri(this.ScreenName, this.RequestedItems, this.IncludeRetweets);
        }
        /// <summary>
        /// The BuildStatusRequestUri method is used to build the call uri for retrieving a specific user's tweets 
        /// </summary>
        /// <returns>The fully constructed uri (with parameters)</returns>
        private static string BuildStatusRequestUri(string screenName, int requestedItems, bool includeRetweets)
        {
            return string.Format(ProjectSettings.Default.TwitterUrlTemplate_GetStatuses, screenName, requestedItems.ToString(), includeRetweets.ToString());
        }
        #endregion

        #region BuildRateLimitRequestUri
        // There are no parameters for this request. API documentation: https://dev.twitter.com/docs/api/1/get/account/rate_limit_status

        /// <summary>
        /// The BuildRateLimitRequestUri method is used to build the call uri for retrieving connection throttling information
        /// </summary>
        /// <returns>The fully constructed uri (with parameters)</returns>
        private static string BuildRateLimitRequestUri()
        {
            return ProjectSettings.Default.TwitterUrlTemplate_GetRateLimit;
        }
        #endregion

        #region BuildUserLookupRequestUri
        /* Fields as listed in the API documentation (https://dev.twitter.com/docs/api/1/get/users/lookup):
         * screen_name
         * user_id // not in use for this component
         * include_entities // will be hard-coded to false
        */

        /// <summary>
        /// The BuildUserLookupRequestUri method is used to build the call uri for validating a screen name
        /// This overload assumes that the screen name property has been assigned.
        /// </summary>
        /// <returns>The fully constructed uri (with parameters)</returns>
        private string BuildUserLookupRequestUri()
        {
            return BuildUserLookupRequestUri(this.ScreenName);
        }
        /// <summary>
        /// The BuildUserLookupRequestUri method is used to build the call uri for validating a screen name
        /// </summary>
        /// <returns>The fully constructed uri (with parameters)</returns>
        private static string BuildUserLookupRequestUri(string screenName)
        {
            return string.Format(ProjectSettings.Default.TwitterUrlTemplate_GetUserInfo, screenName);
        }
        #endregion
        #endregion

        #region " Twitter Communication "
        private string GetTwitterResponse(RequestType type, string method, string auth, string content)
        {
            string uri = GetRequestUri(type);
            if (!certValidationComplete) { SetCertValidationCallbackDelegate(); }
            HttpWebRequest request = WebRequest.Create(uri) as HttpWebRequest;
            request.Method = method;

            if (!string.IsNullOrEmpty(auth)) { request.Headers.Add("Authorization", auth); }

            request.ContentType = "application/x-www-form-urlencoded;charset=UTF-8";
            request.Headers.Add("Accept-Encoding", "gzip");

            if (!string.IsNullOrEmpty(content))
            {
                byte[] byteArray = Encoding.UTF8.GetBytes(content);
                request.ContentLength = byteArray.Length;

                Stream dataStream = request.GetRequestStream();

                dataStream.Write(byteArray, 0, byteArray.Length);
                dataStream.Close();
            }

            HttpWebResponse compressedResponse = null;
            GZipStream uncompressedResponse = null;
            StreamReader reader = null;

            try
            {
                compressedResponse = request.GetResponse() as HttpWebResponse;
                uncompressedResponse = new GZipStream(compressedResponse.GetResponseStream(), CompressionMode.Decompress);
                reader = new StreamReader(uncompressedResponse);
                string responseFromServer = reader.ReadToEnd();

                return responseFromServer;
            }
            catch (System.Net.WebException webEx)
            {
                // Examine the HTTP headers to determine if we should throw a custom error
                switch (((HttpWebResponse)webEx.Response).StatusCode.ToString())
                {
                    case "NotFound":
                        switch (type)
                        {
                            case RequestType.GetListTweets:
                                throw new InvalidUserOrListException(this.displayLocaleId);
                            default:
                                throw new InvalidUserException(this.displayLocaleId);
                        }
                    case "429":
                        RateLimit limit = GetRateLimit();
                        throw new RateLimitExceededException(limit.ResetTime, this.displayLocaleId);
                    case "Unauthorized":
                        // If for some reason the current access token is no longer valid, try to just get another one and proceed
                        ClearCache(this.CACHE_NAME_ACCESS_TOKEN);
                        return GetTwitterResponse(type, method, true, content);
                    default:
                        throw;
                }
            }
            finally
            {
                if (reader != null)
                {
                    reader.Close();
                    reader = null;
                }

                if (uncompressedResponse != null)
                {
                    uncompressedResponse.Close();
                    uncompressedResponse = null;
                }
                if (compressedResponse != null)
                {
                    compressedResponse.Close();
                    compressedResponse = null;
                }

                request = null;
            }
        }

        private string GetTwitterResponse(RequestType type, string method, bool authNeeded, string content)
        {
            try
            {
                string auth = string.Empty;

                if (authNeeded)
                {
                    auth = this.GetCache(this.CACHE_NAME_ACCESS_TOKEN) as string;
                    if (string.IsNullOrEmpty(auth))
                    {
                        auth = GetTwitterAccessToken();
                    }

                    auth = "Bearer " + auth;
                }

                return this.GetTwitterResponse(type, method, auth, content);
            }
            catch (System.Net.WebException webEx)
            {
                if (((HttpWebResponse)webEx.Response).StatusCode.ToString() == "Forbidden")
                {
                    // This error is thrown when the provided credentials are incorrect
                    throw new InvalidAuthenticationException(this.displayLocaleId);
                }
                else
                {
                    // If the credentials are correct, then something unexpected happened
                    throw;
                }
            }
        }
        #endregion

        #region " Helper Methods "
        private string GetTwitterAccessToken()
        {
            string consumerKeyEncoded = HttpUtility.UrlEncode(this.Twitter_ConsumerKey);
            string consumerSecretEncoded = HttpUtility.UrlEncode(this.Twitter_ConsumerSecret);

            string val = Convert.ToBase64String(ASCIIEncoding.ASCII.GetBytes(consumerKeyEncoded + ":" + consumerSecretEncoded));

            string content = "grant_type=client_credentials";
            string auth = "Basic " + val;

            string response = this.GetTwitterResponse(RequestType.GetAccessToken, "POST", auth, content);
            TwitterResponse_AccessToken results = Common.DeserializeJson<TwitterResponse_AccessToken>(response);
            SetCache(this.CACHE_NAME_ACCESS_TOKEN, results.AccessToken);
            return results.AccessToken;
        }

        /// <summary>
        /// The ParseStatusMessage method takes in the raw "tweet" and changes screen name references, hash tags and urls to clickable links.
        /// </summary>
        /// <param name="status">The raw tweet</param>
        /// <returns>The formatted tweet with href tags embedded</returns>
        private static string ParseStatusMessage(string status)
        {
            string tempStatus = status;

            tempStatus = Regex.Replace(tempStatus, @"(http?://([-\w\.]+)+(/([\w/_\.]*(\?\S+)?(#\S+)?)?)?)", "<a href='$1' target='_blank'>$1</a>"); // make sure that non-ssl urls are processed first
            tempStatus = Regex.Replace(tempStatus, @"(htts?://([-\w\.]+)+(/([\w/_\.]*(\?\S+)?(#\S+)?)?)?)", "<a href='$1' target='_blank'>$1</a>"); // make sure that all urls are processed before screen names and hash tags
            tempStatus = Regex.Replace(tempStatus, @"\s+#(\w+)", " <a href='http://search.twitter.com/search?q=%23$1' target='_blank'>#$1</a>");
            tempStatus = Regex.Replace(tempStatus, @"@(\w+)", "@<a href='http://twitter.com/$1' target='_blank'>$1</a>");

            return tempStatus;
        }

        /// <summary>
        /// The ParseTwitterDate method is used to convert the date returned from the Twitter API into
        /// an actual .NET date object. This method assumes that the passed in date is UTC and also returns in UTC format
        /// </summary>
        /// <param name="dateString"></param>
        /// <returns></returns>
        private static DateTime ParseTwitterDate(string dateString)
        {
            // Sample Date: Wed Sep 21 17:11:08 +0000 2011
            DateTime twitterDate = DateTime.ParseExact(dateString, ProjectSettings.Default.FormatString_TwitterDates, null, System.Globalization.DateTimeStyles.AdjustToUniversal);
            return DateTime.SpecifyKind(twitterDate, DateTimeKind.Utc);
        }

        /// <summary>
        /// The DetermineDisplayDiff method is used to create Twitter-like diplay times for tweets.
        /// The method will change an actual date to n minutes ago, just like on the Twitter page.
        /// This is a very simple implementation and does not try to account for how far into an hour
        /// the time difference might be.
        /// </summary>
        /// <param name="utcDateToCompare"></param>
        /// <returns></returns>
        internal string DetermineDisplayDiff(DateTime utcDateToCompare)
        {
            TimeSpan span = new TimeSpan(DateTime.UtcNow.Ticks - utcDateToCompare.Ticks);

            if (span.Days > 0)
            {
                return utcDateToCompare.ToString(ProjectSettings.Default.FormatString_DisplayDates);
            }
            else if (span.Hours > 1)
            {
                return span.Hours + " " + Common.GetResourceValue("TimeSpan_HoursAgo", this.displayLocaleId);
            }
            else if (span.Hours == 1)
            {
                return span.Hours + " " + Common.GetResourceValue("TimeSpan_HourAgo", this.displayLocaleId);
            }
            else if (span.Minutes > 1)
            {
                return span.Minutes + " " + Common.GetResourceValue("TimeSpan_MinutesAgo", this.displayLocaleId);
            }
            else if (span.Minutes == 1)
            {
                return span.Minutes + " " + Common.GetResourceValue("TimeSpan_MinuteAgo", this.displayLocaleId);
            }
            else
            {
                return Common.GetResourceValue("TimeSpan_MomentsAgo", this.displayLocaleId);
            }
        }

        /// <summary>
        /// This method is used to set the cert validation callback. This is necessary when calling Twitter using ssl and 
        /// working with SharePoint as SharePoint maintains its own validation lists and calls to the secure Twitter API fail
        /// </summary>
        private void SetCertValidationCallbackDelegate()
        {
            certValidationComplete = true;

            ServicePointManager.ServerCertificateValidationCallback +=
                delegate(
                    object sender,
                    X509Certificate certificate,
                    X509Chain chain,
                    SslPolicyErrors sslPolicyErrors)
                {
                    return true;
                };
        }
        #endregion
    }

    #region " Custom Objects "

    public class RateLimit
    {
        public int Limit { get; set; }
        public int RemainingLimit { get; set; }
        public DateTime ResetTime { get; set; }
    }
    #endregion

    #region " Custom Exceptions "
    [Serializable]
    public class InvalidUserException : System.Net.WebException
    {
        private string message;

        public override string Message
        {
            get { return this.message; }
        }

        public InvalidUserException(int localeId)
        {
            this.message = Common.GetResourceValue("ExceptionMessage_InvalidScreenName", localeId);
        }

        public InvalidUserException() : base() { }
        public InvalidUserException(string message, Exception innerException) : base(message, innerException) { }
        protected InvalidUserException(SerializationInfo info, StreamingContext context) : base(info, context) { }

        [System.Security.Permissions.SecurityPermission(SecurityAction.LinkDemand, Flags = SecurityPermissionFlag.SerializationFormatter)]
        public override void GetObjectData(SerializationInfo info, StreamingContext context)
        {
            base.GetObjectData(info, context);
        }

    }

    [Serializable]
    public class InvalidUserOrListException : System.Net.WebException
    {
        private string message;

        public override string Message
        {
            get { return this.message; }
        }

        public InvalidUserOrListException(int localeId)
        {
            this.message = Common.GetResourceValue("ExceptionMessage_InvalidUserOrListName", localeId);
        }

        public InvalidUserOrListException() : base() { }
        public InvalidUserOrListException(string message) : base(message) { }
        public InvalidUserOrListException(string message, Exception innerException) : base(message, innerException) { }
        protected InvalidUserOrListException(SerializationInfo info, StreamingContext context) : base(info, context) { }

        [System.Security.Permissions.SecurityPermission(SecurityAction.LinkDemand, Flags = SecurityPermissionFlag.SerializationFormatter)]
        public override void GetObjectData(SerializationInfo info, StreamingContext context)
        {
            base.GetObjectData(info, context);
        }
    }

    [Serializable]
    public class RateLimitExceededException : System.Exception
    {
        private string message;

        public override string Message
        {
            get { return this.message; }
        }

        public RateLimitExceededException(DateTime reconnectAllowedAt, int localeId)
        {
            this.message = Common.GetResourceValue("ExceptionMessage_TwitterConnectionsExceeded", localeId) + " " + reconnectAllowedAt.ToString();
        }

        public RateLimitExceededException() : base() { }
        public RateLimitExceededException(string message) : base(message) { }
        public RateLimitExceededException(string message, Exception innerException) : base(message, innerException) { }
        protected RateLimitExceededException(SerializationInfo info, StreamingContext context) : base(info, context) { }

        [System.Security.Permissions.SecurityPermission(SecurityAction.LinkDemand, Flags = SecurityPermissionFlag.SerializationFormatter)]
        public override void GetObjectData(SerializationInfo info, StreamingContext context)
        {
            base.GetObjectData(info, context);
        }
    }

    [Serializable]
    public class InvalidAuthenticationException : System.Net.WebException
    {
        private string message;

        public override string Message
        {
            get { return this.message; }
        }

        public InvalidAuthenticationException(int localeId)
        {
            this.message = Common.GetResourceValue("ExceptionMessage_AuthInvalid", localeId);
        }

        public InvalidAuthenticationException() : base() { }
        public InvalidAuthenticationException(string message, Exception innerException) : base(message, innerException) { }
        protected InvalidAuthenticationException(SerializationInfo info, StreamingContext context) : base(info, context) { }

        [System.Security.Permissions.SecurityPermission(SecurityAction.LinkDemand, Flags = SecurityPermissionFlag.SerializationFormatter)]
        public override void GetObjectData(SerializationInfo info, StreamingContext context)
        {
            base.GetObjectData(info, context);
        }

    }

    #endregion
}